package pl.edu.pw.elka.tinyrepo.gate.descriptor;

import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import pl.edu.pw.elka.tinyrepo.common.exceptions.ContentAlreadyExistException;
import pl.edu.pw.elka.tinyrepo.common.exceptions.WrongPathException;
import sun.security.krb5.internal.crypto.Des;


public class CatalogDescriptor implements Descriptor {
   
   //private Long size = 0L;
   /** Zbior potomkow w drzewie katalogow. Mapa ze nazwy do Descriptor, dlatego
    *  ze nazwa jest potrzebna tylko po to aby odnalezc plik w strukturze katalogow,
    *  do niczego wiecej nie jest potrzebna. Dlatego nie warto dawac osobnego pola
    *  w np fileDescriptor na nazwe. Poza tym struktura mapy z nazw na deskryptor pliku
    *  pozwala tworzyc dowiazania symboliczne (tzn. ten sam plik moze byc reprezentowany
    *  przez rozne nazwy). */
   private Map<String, Descriptor> childs = new HashMap<String, Descriptor>();
   
   /**
    * Tworzy deskryptor katalogu o zadanej nazwie.
    * 
    * @param name
    */
   public CatalogDescriptor() {
   }
   
   /**
    * Dodaje obiekt o podanym deskryptorze do podanej sciezki drzewa katalogow.
    * np /katalog/plik.txt doda plik do folderu katalog znajdujacego sie bezposrednio 
    * w danym folderze reprezentowanym przez this. 
    *  
    * @param child
    * @param path
    * @throws WrongPathException 
    * @throws ContentAlreadyExistException 
    */
   public synchronized void addChildDescriptor(Descriptor child, String path) 
                  throws FileNotFoundException, WrongPathException, ContentAlreadyExistException {
      Object found[] = parsePath(path);
      String lastToken = (String) found[1];
      CatalogDescriptor it = (CatalogDescriptor) found[0];
      if(lastToken == null) //wstawiono pusty path lub /
         throw new WrongPathException("Empty path");
      // w it jest katalog docelowy, last token zawiera nazwe pliku
      if(it.childs.containsKey(lastToken))
         throw new ContentAlreadyExistException("Content already exist.");
      it.childs.put(lastToken, child);
      System.out.println("Inserted: "+lastToken);
   }
   
   /**
    * Zwraca obiekt okreslony sciezka wzgledna do pliku.
    * Np. dla /katalog/plik.txt zwroci dany plik.
    *  
    * @param child
    * @param path
    * @return UWAGA - zwraca REFERENCJE na deskryptor, wiec jakiekolwiek zmiany w deskryptorze musza byc
    *         synchronizowane.
    * @throws FileNotFoundException 
    * @throws WrongPathException 
    */
   public Descriptor getChildDescriptor(String path) throws FileNotFoundException, WrongPathException {
      Object found[] = parsePath(path);
      String lastToken = (String) found[1];
      if(lastToken == null) // wstawiono pusty path lub /
         return this; // zwroc deskryptor roota
      CatalogDescriptor it = (CatalogDescriptor) found[0];
      if(!it.childs.containsKey(lastToken))
         throw new FileNotFoundException("No such file or directory.");
      return it.childs.get(lastToken);
   }
   
   /**
    * Sprawdza czy istnieje plik o podanej sciezce
    *
    * @param path
    * @return
    */
   public synchronized Boolean contains(String path) throws WrongPathException {
      try {
         getChildDescriptor(path);
      } catch (FileNotFoundException e) {
         return false;
      }
      return true;
   }
   
   /**
    * Usuwa deskryptor wskazany sciezka. Dziala rowniez dla katalogow :).
    * 
    * @param path
    * @return
    * @throws WrongPathException 
    * @throws FileNotFoundException 
    */
   public synchronized void removeChildDescriptor(String path) throws WrongPathException, FileNotFoundException {
      Object found[] = parsePath(path);
      String lastToken = (String) found[1];
      if(lastToken == null)
         throw new WrongPathException("You can not remove root directory");
      CatalogDescriptor it = (CatalogDescriptor) found[0];
      if(!it.childs.containsKey(lastToken))
         throw new FileNotFoundException("No such file or directory");
      // usuwaniem z DS zajmuje sie RepoDescriptor
      Descriptor d = it.childs.get(lastToken);
      it.childs.remove(lastToken);
   }
   
   
   
   /**
    * Przeniesienie elementu w strukturze katalogow.
    * 
    * FIXME nie jest sprawdzane czy src == dest - tylko wzgledy wydajnosciowe
    * 
    * @param src
    * @param dest
    * @throws FileNotFoundException
    * @throws WrongPathException
    * @throws ContentAlreadyExistException
    */
   public synchronized void moveChildDescriptor(String src, String dest) throws FileNotFoundException, WrongPathException, ContentAlreadyExistException {
     if(contains(dest))
        throw new ContentAlreadyExistException("while moving");
     Descriptor d = getChildDescriptor(src); 
     removeChildDescriptor(src); // FIXME przy probie przeniesienia root katalogu wypisze ze proba usnieca root katalogu
     addChildDescriptor(d, dest);
   }
   
   /**
    * FIXME - tworzy plytka kopie pliku/katalogu (dowiazanie symboliczne), jednak
    * poki w projekcie nie ma LOCK/UNLOCK to nie ma problemu
    * 
    * @param src
    * @param dest
    * @throws FileNotFoundException
    * @throws WrongPathException
    * @throws ContentAlreadyExistException
    */
   public synchronized void copyChildDescriptor(String src, String dest) throws FileNotFoundException, WrongPathException, ContentAlreadyExistException {
      if(contains(dest))
         throw new ContentAlreadyExistException("while copying");
      Descriptor d = getChildDescriptor(src);
      addChildDescriptor(d, dest); 
   }
   
   
   
   @Override
   public Long getSize() { // FIXME malo wydajne, przegladanie calej struktury drzewiastej 
      Long result = 0L;
      for(String k : childs.keySet()) {
         result += childs.get(k).getSize();
      }
      return result;
   }
   
   @Override
   public synchronized void setSize(Long size) throws UnsupportedOperationException {
      throw new UnsupportedOperationException(); // nie mozna ustawic rozmiaru danego katalogu ktory jest wyznaczony przez sume jego dzieci
   }
   
   /**
    * FIXME Uwaga - korzystac z rozwaga! - metoda ta redukuje hermetyzacje klasy CatalogDescriptor 
    * @return
    */
   public synchronized Map<String, Descriptor> getChilds () {
      return childs;
   }
   
   /**
    * Zwraca pare obiektow: nazwe pliku wskazanego w sciezce oraz katalog nadrzedny dla tego pliku.
    * 
    * @param path - sciezka.
    * @return Object[0] - katalog nadrzedny, Object[1] - nazwa pliku w tym katalogu nadrzednym
    * @throws WrongPathException 
    */
   private Object[] parsePath(String path) throws WrongPathException {
      CatalogDescriptor it = this;
      StringTokenizer t = new StringTokenizer(path, "/");
      String lastToken = null;
      while(t.hasMoreTokens()) { // znajdz bezposredni katalog
         lastToken = t.nextToken();
         if(!t.hasMoreTokens())
            break; // doszedl do ostatniego elementu w sciezce
         if(!it.childs.containsKey(lastToken)) // jezeli podkatalogu nie ma
            throw new WrongPathException("No such directory: "+lastToken+" for: "+path); // zgloszenie ze podanego podkkatalogu nie ma
         Descriptor d = it.childs.get(lastToken);
         if(d.getClass() != CatalogDescriptor.class) // jezeli na sciezce pojawilo sie cos innego niz katalog
            throw new WrongPathException("Wrong element in path.");
         it = (CatalogDescriptor) d;
      }
      Object ret[] = new Object[2];
      ret[0] = it;
      ret[1] = lastToken;
      return ret;
   }
   
   
   
   /**
    * Tylko do debugowania.
    * 
    * @param c
    */
   public static void printCatalog(String c, CatalogDescriptor d) {
      System.out.println("Katalog: "+c);
      for(String s: d.childs.keySet()) {
         if(d.childs.get(s).getClass() == FileDescriptor.class)
            System.out.println("Plik: "+s+" id: "+((FileDescriptor)d.childs.get(s)).getId());
         else printCatalog(s, (CatalogDescriptor)(d.childs.get(s)));
      }
      System.out.println("end Katalog: "+c);
   }
   
   /**
    * Id wyszukiwania destination jest jego nazwa - wiec trzeba zapewnic, zeby nazwa data serwera byla zawsze taka 
    * sama !!!
    * 
    * @param dest
    * @param d
    * @return
    */
   static long computeDiskUsageForDestination(String dest, CatalogDescriptor d) {
      long sum = 0;
      for(String s: d.childs.keySet()) {
         Descriptor dd = d.childs.get(s);
         if(dd.getClass() == FileDescriptor.class && ((FileDescriptor)dd).getDestination().equals(dest))
               sum += dd.getSize();
         else if(dd.getClass() == CatalogDescriptor.class)
            sum += computeDiskUsageForDestination(dest, (CatalogDescriptor)(dd));
      }
      return sum;
   }
}
